package com.hobart.learning.lagou.edu.lagouframework.servlet;

import com.hobart.learning.lagou.edu.lagouframework.annotation.*;
import com.hobart.learning.lagou.edu.lagouframework.pojo.Handler;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: Hobart Mr.Bai
 * @create: 2021-03-30 00:51
 * @description:
 * @Version 1.0
 **/
public class LagouDispatherServlet extends HttpServlet {

    // hobartmvc.properties 配置
    private Properties properties = new Properties();

    // 缓存扫描到的 所有全限定类名
    private List<String> classNames = new ArrayList<>();

    // ioc 容器
    private Map<String, Object> iocMap = new HashMap<>();

    // handlerMapping
    //private Map<String, Method> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.加载配置文件 位置
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        // 加载配置文件 内容
        doLoadConfig(contextConfigLocation);

        // 2.扫描相应的类
        doScan(properties.getProperty("scanPackage"));

        // 3.初始化bean对象，(实现Ioc容器，基于注解)
        doInstance();

        // 4.实现依赖注入
        doAutowired();

        // 5.构造一个handlerMapping处理器映射器，将配制好的url和method建立映射关系
        initHandlerMapping(config.getServletContext());

        System.out.println(" Hobart Mvc 初始化 完成！ " );
        // 6.等待请求进入，处理请求
        iocMap.forEach((key, value) -> {
            System.out.println("key = " + key + ", value = " + value);
        });
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求
        resp.setCharacterEncoding("GBK");
        PrintWriter writer = resp.getWriter();

        Handler handler = getHandler(req);
        if(handler == null){
            writer.println("404 Not Found");
        } else {

            Method method = handler.getMethod();

            // 参数绑定
            // 获取所有 参数类型数组， 这个数组的长度 就是我们要传入的 数组的 长度
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 根据上述数组长度 创建 一个新的数组， 用于反射调用
            Object[] argsArray = new Object[parameterTypes.length];

            String[] security = handler.getSecurity();

            // 向 argsArray 中赋值，并保证 argsArray 中的值与 method方法 的参数顺序一致
            Map<String, String[]> parameterMap = req.getParameterMap();

            // 遍历 req 中所有的 参数
            for (Map.Entry<String, String[]> paramEntry : parameterMap.entrySet()) {
                String entryKey = paramEntry.getKey();

                Object[] entryValue = paramEntry.getValue();
                String value = StringUtils.join(entryValue, ",");

                int flag = 0;
                if(StringUtils.equals("userName",entryKey)){
                    for (String s : security) {
                        if(value.contains(s)){
                            flag=1;
                            break;
                        }
                    }
                }

                if(flag==0){
                    writer.print("权限不足");
                    writer.flush();
                    return;
                }

                // 如果参数 和方法中的参数匹配上， 填充数据
                if(!handler.getParamIndexMapping().containsKey(entryKey)){
                    continue;
                }

                // 方法形参 有相应的参数，找到它的索引位置， 对应把参数值放进 argsArray
                Integer index = handler.getParamIndexMapping().get(entryKey);
                // 把前端传入的值放进  argsArray
                argsArray[index] = value;

            }

            Integer indexRequest = handler.getParamIndexMapping().get(HttpServletRequest.class.getName());
            argsArray[indexRequest] = req;

            Integer indexResponse = handler.getParamIndexMapping().get(HttpServletResponse.class.getName());
            argsArray[indexResponse] = resp;

            // 最终调用 handler 的 method 的方法
            try {
                Object invoke = method.invoke(handler.getController(), argsArray);
                writer.println(invoke)
;            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

        writer.print("测试");
        writer.flush();
    }

    /**
     * 加载配置
     * @param contextConfigLocation 配置文件位置
     */
    private void doLoadConfig(String contextConfigLocation) {

        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     *  扫描类
     * @param scanPackage 需要扫描的包的路径
     *                    scanPackage: com.hobart.learning.lagou.edu.demo --> com\hobart\learning\lagou\edu\demo
     */
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath()
                + scanPackage.replaceAll("\\.", "/");
        File scanPackageFile = new File(scanPackagePath);

        File[] files = scanPackageFile.listFiles();

        for (File file : files) {
            if(file.isDirectory()) { // 子包
                //递归
                doScan(scanPackage+"."+file.getName());
            } else if (file.getName().endsWith(".class")){
                String className = scanPackage+"."+ file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }

    }

    /**
     * ioc容器 初始化bean
     * 基于classNames 缓存类的全限定类名，以及反射机制，完成实体类的 创建和管理
     */
    private void doInstance(){
        if (classNames.size() == 0) {
            return;
        }

        try {
            for (String className : classNames) {
                // 反射
                Class<?> aClass = Class.forName(className);
                // 区分 Controller  Service
                if (aClass.isAnnotationPresent(LagouController.class)) {
                    // 类名
                    String simpleName = aClass.getSimpleName();
                    String lowerCaseFirstOne = toLowerCaseFirstOne(simpleName);
                    // 类权限定名
                    String name = aClass.getName();
                    Object obj = aClass.getConstructor().newInstance();

                    iocMap.put(name, obj);
                    iocMap.put(lowerCaseFirstOne, obj);

                    LagouController annotation = aClass.getAnnotation(LagouController.class);

                    String beanName = annotation.value();
                    if (beanName != null && !"".equals(beanName)) {
                        iocMap.put(beanName, obj);
                    }

                } else if (aClass.isAnnotationPresent(LagouService.class)) {
                    // 类名
                    String simpleName = aClass.getSimpleName();
                    String lowerCaseFirstOne = toLowerCaseFirstOne(simpleName);

                    // 类权限定名
                    String name = aClass.getName();
                    Object obj = aClass.getConstructor().newInstance();

                    iocMap.put(name, obj);
                    iocMap.put(lowerCaseFirstOne, obj);


                    LagouService annotation = aClass.getAnnotation(LagouService.class);

                    // 如果注解有 value 取value 的值 注入
                    String beanName = annotation.value();
                    if (StringUtils.isNotBlank(beanName)) {
                        iocMap.put(beanName, obj);
                    }

                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        String interfaceName = anInterface.getName();

                        String simpleName1 = anInterface.getSimpleName();

                        // 以 接口 注入
                        iocMap.put(interfaceName, obj);
                        iocMap.put(toLowerCaseFirstOne(simpleName1), obj);

                    }

                } else {
                    continue;
                }


            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    /**
     * 依赖注入
     */
    private void doAutowired() {
        if(iocMap.isEmpty()){
            return;
        }
        // 容器中有对象 再进行 依赖注入

        // 遍历ioc 中对象中的字段，是否有@HobartAutowired注解
        // 如果就维护其依赖关系
        for (Map.Entry<String, Object> mapEntry : iocMap.entrySet()) {
            String key = mapEntry.getKey();
            Object value = mapEntry.getValue();
            // 获取bean对象中的字段信息
            Field[] declaredFields = value.getClass().getDeclaredFields();
            // 遍历处理
            for (Field declaredField : declaredFields) {
                if(!declaredField.isAnnotationPresent(LagouAutowired.class)){
                    continue;
                }

                LagouAutowired annotation = declaredField.getAnnotation(LagouAutowired.class);

                String beanName = annotation.value();// 需要注入的bean 的id
                if (StringUtils.isBlank(beanName)) {
                    // 没有配置具体的 bean id 那就需要根据当前字段类型注入 （接口注入）
                    beanName = declaredField.getType().getName();
                }

                // 开启赋值
                declaredField.setAccessible(true);

                try {
                    //
                    declaredField.set(value, iocMap.get(beanName));

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /**
     *  初始化 handlerMapping
     * @param servletContext
     */
    private void initHandlerMapping(ServletContext servletContext) {
        if(iocMap.isEmpty()) {
            return;
        }

        String contextPath = servletContext.getContextPath();

        for (Map.Entry<String, Object> mapEntry : iocMap.entrySet()) {

            // 获取 IOC 中当前遍历对象的class 类型
            Class<?> aClass = mapEntry.getValue().getClass();

            if(!aClass.isAnnotationPresent(LagouController.class)) {
                continue;
            }

            if(aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                LagouRequestMapping controllerAnnotation = aClass.getAnnotation(LagouRequestMapping.class);
                String controllerMapping = controllerAnnotation.value(); // 指向 /user

                // 获取方法
                Method[] methods = aClass.getMethods();

                for (Method method : methods) {

                    StringBuffer urlBuffer = new StringBuffer();

                    if(StringUtils.isNotBlank(contextPath)){
                        urlBuffer.append(contextPath);
                    }

                    if (StringUtils.isNotBlank(controllerMapping)) {
                        urlBuffer.append(controllerMapping);
                    }

                    // 方法没有标识 HobartRequsetMapping， 就不处理
                    if (!method.isAnnotationPresent(LagouRequestMapping.class)) {
                        continue;
                    }
                    // 如果有标识就处理
                    LagouRequestMapping methodAnnotation = method.getAnnotation(LagouRequestMapping.class);
                    String methodMapping = methodAnnotation.value(); // 指向 /getUserInfo
                    if (StringUtils.isNotBlank(methodMapping)) {
                        urlBuffer.append(methodMapping);
                    }

                    // 建立 url 与method 之间映射的关系
                    //handlerMapping.put(urlBuffer.toString(), method);

                    // 把 method 所有信息 及 URL 封装到handler 中
                    Handler handler = new Handler(mapEntry.getValue(), method, Pattern.compile(urlBuffer.toString()));

                    // 计算方法的参数位置
                    Parameter[] parameters = method.getParameters();

                    int i = 0;
                    for (Parameter parameter : parameters) {
                        if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class ){
                            handler.getParamIndexMapping().put(parameter.getType().getName(), i++);
                        } else {
                            handler.getParamIndexMapping().put(parameter.getName(), i++);
                        }
                    }

                    if (!method.isAnnotationPresent(LagouSecurity.class)) {
                        continue;
                    }

                    // 如果有标识就处理
                    LagouSecurity security = method.getAnnotation(LagouSecurity.class);
                    String[] methodSecurity = security.value();
                    handler.setSecurity(methodSecurity);

                    // 建立 url 与method 之间映射的关系
                    handlerMapping.add(handler);

                }
            }

        }

    }

    private Handler getHandler(HttpServletRequest req) {
        if(CollectionUtils.isEmpty(handlerMapping)){
            return null;
        }
        
        String requestURI = req.getRequestURI();

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if(!matcher.matches()){
                continue;
            }
            return handler;
        }

        return null;
    }

    private String toLowerCaseFirstOne(String str){
        if(str==null||"".equals(str)){
            return "";
        }
        if(Character.isLowerCase(str.charAt(0))) {
            return str;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
        }
    }


    private InputStream getResourceAsStream(String resourcePath){
        System.out.println("resourcePath = " +  LagouDispatherServlet.class.getResource(resourcePath));
        InputStream resourceAsStream = LagouDispatherServlet.class.getResourceAsStream(resourcePath);
        return resourceAsStream;
    }
}
